Skip to content

Add MCP server registry UI: version management, structured server.json, tools, compare view, aliases, and tags#289

Merged
openshift-merge-bot[bot] merged 5 commits into
opendatahub-io:mcp-registry-prototypefrom
nananosirova:RHOAIENG-65775-2
Jun 13, 2026
Merged

Add MCP server registry UI: version management, structured server.json, tools, compare view, aliases, and tags#289
openshift-merge-bot[bot] merged 5 commits into
opendatahub-io:mcp-registry-prototypefrom
nananosirova:RHOAIENG-65775-2

Conversation

@nananosirova

@nananosirova nananosirova commented Jun 9, 2026

Copy link
Copy Markdown

Summary

MCP server registry UI for RHOAIENG-65775. This PR adds version management, a compare view, latest version resolution, alias management, server-level tags, and structured server.json rendering.

Key features:

  • Create MCP server and create version modals with JSON validation, tag editor, and status selection
  • Version detail view with tabbed layout (Configuration, Tools, Access Bindings)
  • Structured server.json display: packages as expandable rows with env vars, remotes with copy buttons, raw JSON toggle
  • Tools tab with expandable tool rows showing inputSchema/outputSchema and annotations
  • Compare view with side-by-side JSON diff and version selector buttons
  • Latest alias resolution via GET /aliases/latest with pinned/fallback color coding
  • Server-level tags with edit modal
  • MCP servers table and card grid with latest version tag

Version detail tabs:

  • Configuration: packages rendered as expandable rows (registry type tag + identifier), each expanding to show version, transport type, runtimeHint, and environmentVariables with isRequired/isSecret badges. Remotes shown as compact rows with transport tag + URL + copy button. "Show more / Show less" for long lists. Raw server.json toggle with copy button at the bottom.
  • Tools: expandable tool rows showing name, description, inputSchema/outputSchema as formatted JSON, and annotation badges. Raw tools JSON toggle with copy button.
  • Access Bindings: existing bindings list for the current version.
Screen.Recording.2026-06-12.at.7.24.36.PM.mov

Relates to RHOAIENG-65775, RHOAIENG-65774, RHOAIENG-65780, RHOAIENG-67491

Upstream / Downstream Impact

  • Also affects upstream mlflow/mlflow

Upstream backend PRs: MCP registry REST API, MCP registry REST client

Testing

  • Unit tests
  • Manual testing

8 test suites, 106 tests total - all passing. Manual testing with persistent SQLite backend.


Related Issues/PRs

Relates to RHOAIENG-65775, RHOAIENG-65774, RHOAIENG-65780, RHOAIENG-67491

Structured server.json rendering (new ServerJSONSection component):

  • Replaces raw JSON.stringify block with structured UI sections
  • Packages: expandable rows with registry type tag, identifier (truncated), version, transport/runtimeHint, and environmentVariables (show more/less for >5 items)
  • Remotes: rows with transport type tag, URL in monospace, and copy-to-clipboard button
  • Raw server.json: toggle button with chevron and copy button

Structured tools rendering (new ToolsSection component):

  • Expandable tool rows with name, title, description
  • inputSchema/outputSchema rendered as formatted JSON blocks with copy buttons
  • Annotation badges for tool metadata
  • Raw tools JSON toggle with copy button

Tabbed version detail layout:

  • Replaced stacked collapsible sections with Tabs component (Configuration, Tools, Access Bindings)
  • Tools tab only shown when tools exist, with count in tab label
  • Description shown as read-only text (immutable per RFC: server_json is immutable after creation)

Version management:

  • Create MCP server modal and create version modal with server.json validation, status selection, source URL, tools JSON, and inline tag editor
  • Version detail with display name editing, metadata (tags) editing via modals
  • Status transitions matching backend (draftactive, activedraft/deprecated, deprecatedactive)
  • Delete version with confirmation modal

Latest version resolution:

  • Resolves via GET /{name}/aliases/latest (pinned or fallback to most recent active)
  • Three-state button: "Set as latest", "Pin as latest", "Unpin latest"
  • Latest alias tag color: turquoise (pinned) vs brown (auto-resolved fallback)

Compare view:

  • Side-by-side server.json diff with word-level highlighting
  • Version selector split buttons, switch sides button
  • Side-by-side metadata grids

Server-level features:

  • Server-level tags with add/edit modal
  • MCP servers table with Name, Last modified, Description, Tags columns
  • MCP server card shows latest version tag

Context:

How is this PR tested?

  • Existing unit/integration tests
  • New unit/integration tests
  • Manual tests

Tests cover: package/remote rendering, expandable package rows with env vars, raw JSON toggle, tab switching, access bindings, version selection, compare view, status transitions, description display (read-only), display name editing, server-level tags, create modals.

Does this PR require documentation update?

  • No.
  • Yes. I've updated:
    • Examples
    • API references
    • Instructions

Does this PR require updating the MLflow Skills repository?

  • No.
  • Yes. Please link the corresponding PR or explain how you plan to update it.

Release Notes

Is this a user-facing change?

  • No.
  • Yes. Give a description of this change to be included in the release notes for MLflow users.

What component(s), interfaces, languages, and integrations does this PR affect?

Components

  • area/tracking: Tracking Service, tracking client APIs, autologging
  • area/models: MLmodel format, model serialization/deserialization, flavors
  • area/model-registry: Model Registry service, APIs, and the fluent client calls for Model Registry
  • area/scoring: MLflow Model server, model deployment tools, Spark UDFs
  • area/evaluation: MLflow model evaluation features, evaluation metrics, and evaluation workflows
  • area/gateway: MLflow AI Gateway client APIs, server, and third-party integrations
  • area/prompts: MLflow prompt engineering features, prompt templates, and prompt management
  • area/tracing: MLflow Tracing features, tracing APIs, and LLM tracing functionality
  • area/projects: MLproject format, project running backends
  • area/uiux: Front-end, user experience, plotting, JavaScript, JavaScript dev server
  • area/build: Build and test infrastructure for MLflow
  • area/docs: MLflow documentation pages

How should the PR be classified in the release notes? Choose one:

  • rn/none - No description will be included. The PR will be mentioned only by the PR number in the "Small Bugfixes and Documentation Updates" section
  • rn/breaking-change - The PR will be mentioned in the "Breaking Changes" section
  • rn/feature - A new user-facing feature worth mentioning in the release notes
  • rn/bug-fix - A user-facing bug fix worth mentioning in the release notes
  • rn/documentation - A user-facing documentation change worth mentioning in the release notes

Is this PR a critical bugfix or security fix that should go into the next patch release?

  • This PR is critical and needs to be in the next patch release
  • This PR can wait for the next minor release

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (4)
  • main
  • master
  • incubation
  • rhoai

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 99bee351-9779-492b-84d5-358152d5203f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size/XL Pull request size: XL label Jun 9, 2026
@nananosirova nananosirova force-pushed the RHOAIENG-65775-2 branch 2 times, most recently from e89633b to a85306a Compare June 9, 2026 18:17
@DaoDaoNoCode DaoDaoNoCode force-pushed the mcp-registry-prototype branch from 6ba364a to aaf82fc Compare June 10, 2026 16:37
@nananosirova nananosirova changed the title Add create MCP server version modal with validation and tags Add MCP server registry UI: detail page, version management, compare view, aliases, and tags Jun 10, 2026
@nananosirova nananosirova force-pushed the RHOAIENG-65775-2 branch 3 times, most recently from ff1d3b2 to 1da37d3 Compare June 11, 2026 00:08
@DaoDaoNoCode DaoDaoNoCode added the rn/none Skip the validate-labeled CI job on the feature branches label Jun 11, 2026
@nananosirova nananosirova force-pushed the RHOAIENG-65775-2 branch 3 times, most recently from 7016c96 to 417418e Compare June 11, 2026 15:00
wordBreak: 'break-word',
}}
>
<code>{baselineJson || 'Empty'}</code>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i18n (Medium): Hardcoded English string 'Empty' — should use <FormattedMessage> or intl.formatMessage() for i18n support.

<code>{baselineJson || intl.formatMessage({ defaultMessage: 'Empty', description: 'Fallback for empty server JSON in compare view' })}</code>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. The Prompts compare view (PromptContentCompare.tsx line 169) also uses a hardcoded 'Empty' string. Both should use intl.formatMessage(), but since the source pattern has the same issue, this is consistent. Can be addressed as a follow-up.

<div
css={{
display: 'grid',
gridTemplateColumns: '100px 1fr',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency (Medium): gridTemplateColumns: '100px 1fr' here but MCPServerVersionDetail.tsx uses '120px 1fr' for the same metadata grid pattern. These should match for visual consistency. Also consider using a shared constant so both stay in sync.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. We aligned this to 120px in our branch (PR #292) to match MCPServerVersionDetail. This PR should pick up the same value.

<Button
componentId="mlflow.mcp_registry.detail.version.edit_display_name"
size="small"
icon={<PencilIcon />}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessibility (High): This icon-only <Button> with <PencilIcon> has no aria-label, making it invisible to screen readers. Same issue on the edit buttons for description (line ~166), status (line ~296), and metadata (line ~353).

Suggested fix — add aria-label like the tags edit button already does:

<Button
  aria-label={intl.formatMessage({
    defaultMessage: "Edit display name",
    description: "Aria label for edit version display name button",
  })}
  icon={<PencilIcon />}
  ...
/>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. Icon-only buttons must have aria-label for screen reader accessibility. We added aria-label to our delete binding button in PR #292 following the same pattern.

onClearLatestError={() => setLatestMutation.reset()}
resolvedLatestVersion={resolvedLatestVersion}
onUpdateDescription={async (description) => {
await MCPRegistryApi.updateMCPServer(serverName, { description });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UX (Medium): This calls updateMCPServer(serverName, { description }) which updates the server-level description, but the button is placed inside the version detail panel. Users may think they're editing a version-level property. Consider either:\n- Moving the description editing to the server-level header area (above the version panel)\n- Adding a visual cue/label that this is the "Server description" not "Version description"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid observation. The server description vs version description distinction can be confusing in the current layout. A label like "Server description" would help. Not a blocker but worth improving.

}, [versions, selectedVersion, viewState.comparedVersion, setComparedVersion, setSelectedVersion]);

useEffect(() => {
setLatestMutation.reset();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (Medium): This resets the mutation state when selectedVersion changes, but doesn't cancel the in-flight mutation. If a user clicks "Set as latest" then quickly switches versions, the mutation continues in the background and its result will apply to the wrong version's UI state.\n\nSuggested fix — also add a comment explaining the eslint-disable:\ntsx\nuseEffect(() => {\n // Reset mutation state when switching versions to avoid showing\n // stale error/success from a different version.\n // setLatestMutation is excluded from deps to avoid infinite loop\n // (reset() changes the mutation reference).\n setLatestMutation.reset();\n}, [selectedVersion]); // eslint-disable-line react-hooks/exhaustive-deps\n\n\nIdeally, also track which version the mutation was initiated for and only show loading/error when it matches the current selectedVersion.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. The eslint-disable comment explaining why setLatestMutation is excluded from deps is good practice. The race condition concern is real but low-risk — the mutation result is only used for UI state (loading spinner, error display) and the cache invalidation ensures data correctness regardless.

const [editingBinding, setEditingBinding] = useState<MCPAccessBinding | undefined>(undefined);
const [deletingBinding, setDeletingBinding] = useState<MCPAccessBinding | undefined>(undefined);
const [editServerDisplayNameVisible, setEditServerDisplayNameVisible] = useState(false);
const [serverDisplayNameSaving, setServerDisplayNameSaving] = useState(false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pattern (Medium): This manually manages serverDisplayNameSaving/Error/Visible state with .then()/.catch()/.finally(), while every other mutation on this page uses React Query hooks. For consistency, consider creating a useUpdateMCPServerDisplayName hook (parallel to useUpdateMCPServerVersionDisplayName) — it would eliminate ~15 lines of manual state management and centralize cache invalidation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. Extracting a useUpdateMCPServerDisplayName React Query hook would align with the pattern used by all other mutations on this page and eliminate manual .then()/.catch()/.finally() state management.

}
if (viewState.comparedVersion && !versions.some((v) => v.version === viewState.comparedVersion)) {
setComparedVersion(
versions[0]?.version === selectedVersion ? (versions[1]?.version ?? '') : (versions[0]?.version ?? ''),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (Low): When only 1 version remains after deletion, versions[1]?.version ?? '' sets comparedVersion to an empty string. This causes the compare view to render a half-empty panel (no version found for '').\n\nSuggested fix: switch back to preview mode when fewer than 2 versions remain:\ntsx\nif (viewState.comparedVersion && !versions.some((v) => v.version === viewState.comparedVersion)) {\n if (versions.length < 2) {\n setPreviewMode();\n } else {\n setComparedVersion(\n versions[0]?.version === selectedVersion ? versions[1]?.version : versions[0]?.version,\n );\n }\n}\n

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. Switching back to preview mode when fewer than 2 versions remain is the correct behavior. The suggested fix is clean.

},
onSuccess: (_data, { serverJson }) => {
const name = serverJson.name;
queryClient.invalidateQueries(['mcp_servers_list']);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency (Medium): These use hardcoded query key strings ('mcp_server', 'mcp_servers_list', etc.) instead of MCP_QUERY_KEYS constants from utils.ts. The existing useInvalidateServerQueries helper and query hooks all use the constants. Also, 'mcp_server_latest_version' has no constant entry in MCP_QUERY_KEYS at all.\n\nSuggested fix:\n1. Add LATEST_VERSION: 'mcp_server_latest_version' to MCP_QUERY_KEYS in utils.ts\n2. Replace all raw strings here with MCP_QUERY_KEYS.* constants\n3. Better yet, use the shared useInvalidateServerQueries() helper instead of manual invalidation

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. We extracted MCP_QUERY_KEYS constants in PR #292 and moved all existing hooks to use them. New hooks in this PR should follow the same pattern. The useInvalidateServerQueries() helper already exists and handles the full invalidation set.

mutationFn: ({ version, displayName }: { version: string; displayName: string }) =>
MCPRegistryApi.updateMCPServerVersion(serverName, version, { display_name: displayName || null }),
onSuccess: () => {
queryClient.invalidateQueries(['mcp_server', serverName]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency (Medium): useUpdateMCPServerVersionDisplayName and useSetLatestVersion (below) manually call queryClient.invalidateQueries() with raw strings and only invalidate a subset of keys. The existing pattern in this same file uses useInvalidateServerQueries() which invalidates all 5 relevant keys. Missing SERVER_BINDINGS and BINDINGS_LIST invalidation could cause stale cache issues.

Suggested fix: use the shared useInvalidateServerQueries() helper for both new hooks, same as useUpdateMCPServerVersionStatus and useDeleteMCPServerVersion already do.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. The useInvalidateServerQueries() helper already exists in this file and handles all 5 keys. New mutations should use it rather than manual invalidateQueries calls with a subset of keys.


const updateMutation = useMutation<unknown, Error, UpdateTagsPayload>({
mutationFn: async ({ toAdd, toDelete, serverName }) => {
return Promise.all([

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication (Medium): This Promise.all([...toAdd.map(set), ...toDelete.map(del)]) + useEditKeyValueTagsModal + diffCurrentAndNewTags wiring is duplicated here, in MCPRegistryPage.tsx, and in useUpdateMCPServerVersionMetadataModal.tsx.

The server-level tag editing (this component and MCPRegistryPage) is structurally identical. Consider extracting a shared useEditMCPServerTagsModal(serverName, onTagsUpdated) hook that both consumers can call, similar to how useUpdateMCPServerVersionMetadataModal encapsulates version-level tag editing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid but acceptable. The tag editing wiring is repeated but each instance has slightly different context (server-level vs version-level tags, different invalidation needs). Extracting a shared helper would require parameterizing the API calls and invalidation, which may add complexity without much gain for 3 call sites.

export const validateServerJson = (value: string): ServerJsonValidationResult => {
const trimmed = value?.trim();
if (!trimmed) {
return { valid: false, error: 'Server definition is required' };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i18n (High): These validation error messages are hardcoded English strings that get surfaced to users via Alert components. All 9 messages in validateServerJson() and validateToolsJson() should use intl.formatMessage().

Since these are pure utility functions, you could either:

  1. Accept intl as a parameter: validateServerJson(json: string, intl: IntlShape)
  2. Return error keys (e.g., 'server_json_required') and map to i18n messages in the component layer

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid but low priority. These are JSON schema validation error messages shown in developer-facing modals (create version form). The messages describe technical JSON structure issues like "server_json must have a name field". Similar validation in the codebase (e.g., form validation in admin components) uses hardcoded English. Worth fixing eventually but not a blocker — the error messages are shown inside Alert components in modals, not in the main UI flow.

switch (action.type) {
case 'setPreviewMode':
return { ...state, mode: MCPServerDetailViewMode.PREVIEW, comparedVersion: undefined };
case 'setCompareMode':

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (Medium): No guard prevents selectedVersion === comparedVersion. A user can click both baseline and compared radios on the same version row, resulting in a self-diff. The setSelectedVersion and setComparedVersion reducers should auto-swap when they'd create a collision:\n\ntsx\ncase 'setSelectedVersion':\n return {\n ...state,\n selectedVersion: action.version,\n // If this would collide with compared, swap them\n comparedVersion: action.version === state.comparedVersion\n ? state.selectedVersion\n : state.comparedVersion,\n };\n

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. Good catch — the Prompts version avoids this because setCompareMode always picks two different versions, but a user could manually click the same row's baseline+compared buttons. The auto-swap suggestion is clean. The Prompts implementation handles this implicitly because the switchSides function swaps them, but there's no guard on direct selection.

server.use(getMockedSearchMCPServerVersionsResponse([deletedVersion]));
renderPage();
await waitFor(() => {
expect(screen.getByText('Viewing version 1')).toBeInTheDocument();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing (Medium): Several new components/hooks in this PR have no dedicated test coverage:

  • MCPServerTagsBox.tsx — tag CRUD save flow untested
  • UpdateVersionDisplayNameModal.tsx — save/error flow and useEffect reset behavior untested
  • MCPServerVersionDiffSelectorButton.tsx — radio accessibility attributes untested
  • useUpdateMCPServerVersionMetadataModal.tsx — mutation calls and query invalidation untested

Also: the existing MCPServerDetailPage.test.tsx tests open modals and check error paths but rarely verify happy-path completion (e.g., save succeeds → modal closes → data refreshes). Consider adding at least one success-flow test per modal.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. PR #292 adds test coverage for compare components (MCPServerVersionCompare, MCPServerVersionDiffSelectorButton) and compare mode integration tests. The new hooks and modals in this PR should have matching coverage.

);
});
},
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency (Low): Same issue as the create mutation — raw query key string 'mcp_server_versions' instead of MCP_QUERY_KEYS.SERVER_VERSIONS. This hook should also use the constants for consistency.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. Should use MCP_QUERY_KEYS.SERVER_VERSIONS constant. Quick fix.

}}
/>
</ScrollablePageWrapper>
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: MCPRegistryPage was updated to use the new ErrorUtils.mlflowServices.MCP_REGISTRY, but this page and MCPAccessBindingDetailPage still use ErrorUtils.mlflowServices.EXPERIMENTS. Should be updated for consistency.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. Both MCPServerDetailPage and MCPAccessBindingDetailPage should use the new ErrorUtils.mlflowServices.MCP_REGISTRY for consistency with MCPRegistryPage.

serverName: string;
toAdd: { key: string; value: string }[];
toDelete: { key: string }[];
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (Medium): onSuccess only invalidates SERVERS_LIST but not MCP_QUERY_KEYS.SERVER for the specific server. When tags are edited from the detail page, the server object stays stale until revisited. The onTagsUpdated callback covers this on the detail page via refetchAll, but the per-server query cache remains stale.

Should also invalidate [MCP_QUERY_KEYS.SERVER, serverName], or better yet use the useInvalidateServerQueries helper.

);
});
},
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency (Medium): This only invalidates SERVER_VERSIONS but not SERVER_LATEST_VERSION. Version metadata changes could affect the latest version entity. Consider using useInvalidateServerQueries() which handles all 5+ query keys, same as the other mutation hooks in this file.

const setTag = isNewServer
? (key: string, value: string) => MCPRegistryApi.setMCPServerTag(name, { key, value })
: (key: string, value: string) =>
MCPRegistryApi.setMCPServerVersionTag(name, version.version, { key, value });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug (Medium): The catch {} block silently swallows all errors from the secondary operations (display name, description, tags). The user sees success and navigates to the detail page, but these fields may not have been applied. At minimum, consider showing a warning notification like "Version created but some metadata could not be saved" so the user knows to retry.

server.use(getMockedSearchMCPServerVersionsResponse([deletedVersion]));
renderPage();
await waitFor(() => {
expect(screen.getByText('Viewing version 1')).toBeInTheDocument();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing (Medium): Good test coverage overall — useCreateMCPServerVersionModal (9 tests), MCPServerVersionCompare (5), MCPServerVersionDiffSelectorButton (5), useMCPServerDetailViewState (8), plus ~200 lines added to MCPServerDetailPage.test.tsx.

Still missing dedicated tests for:

  • useUpdateMCPServerVersionMetadataModal — no test file
  • MCPServerTagsBox — no test file
  • useUpdateMCPServerTags — no test file

These get partial coverage via page-level integration tests, but dedicated unit tests would catch edge cases (e.g., the missing cache invalidation in useUpdateMCPServerTags).

</div>
<div css={{ flex: 1 }}>
<VersionMetadataGrid
version={comparedVersion}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication (Low): The JSON <pre> styling (margin: 0, padding, backgroundColor, borderRadius, overflow, fontSize, fontFamily) is duplicated between MCPServerVersionCompare (the jsonPanelStyles constant) and MCPServerVersionDetail (inline on the <pre> inside CollapsibleSection). Consider extracting into a shared jsonPanelStyles constant in utils.ts or a small JsonCodeBlock component.

Signed-off-by: Nana Nosirova <10577112+nananosirova@users.noreply.github.com>
Signed-off-by: Nana Nosirova <10577112+nananosirova@users.noreply.github.com>
Signed-off-by: Nana Nosirova <10577112+nananosirova@users.noreply.github.com>
Signed-off-by: Nana Nosirova <10577112+nananosirova@users.noreply.github.com>
@nananosirova nananosirova changed the title Add MCP server registry UI: detail page, version management, compare view, aliases, and tags Add MCP server registry UI: version management, structured server.json, tools, compare view, aliases, and tags Jun 12, 2026

@DaoDaoNoCode DaoDaoNoCode left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@openshift-ci

openshift-ci Bot commented Jun 12, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

Approval requirements bypassed by manually added approval.

This pull-request has been approved by: DaoDaoNoCode

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-merge-bot openshift-merge-bot Bot merged commit ce19d9f into opendatahub-io:mcp-registry-prototype Jun 13, 2026
70 of 71 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved lgtm rn/none Skip the validate-labeled CI job on the feature branches size/XL Pull request size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants